#include "../../UberLame_src/NewFix.h"
#include "../../UberLame_src/CallStack.h"
#include <vector>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <conio.h>
#include "../../UberLame_src/Integer.h"
#include "../../UberLame_src/MinMax.h"
#include "../../UberLame_src/StlUtils.h"
#include "../../UberLame_src/Mersene.h"
#include "../../UberLame_src/Vector.h"
#include "../../UberLame_src/Timer.h"
#include "../../UberLame_src/Dir.h"
#include <cuda.h>
#include <cublas.h>

#include "../CuUtils.h"
#include "SMOonGPU.h"

float f_rand()
{
	return float(rand()) / RAND_MAX;
}

Vector3f v_RandGroupPoint(Vector3f v_center, float f_radius)
{
	float f_angle = 2 * f_pi * f_rand();
	float f_angle2 = 2 * f_pi * f_rand();
	f_radius *= sqrt(f_rand());
	return v_center + Vector3f(sin(f_angle) * cos(f_angle2),
							   cos(f_angle) * sin(f_angle2),
							   sin(f_angle) * cos(f_angle2)) * f_radius;
}

class CComparePt {
protected:
	Vector3f m_v_ref;

public:
	CComparePt(Vector3f v_ref)
		:m_v_ref(v_ref)
	{}

	inline bool operator ()(Vector3f v_vec) const
	{
		return fabs(v_vec.x - m_v_ref.x) < 1e-4 ||
			   fabs(v_vec.y - m_v_ref.y) < 1e-4 ||
			   fabs(v_vec.z - m_v_ref.z) < 1e-4;
	}
};

CUcontext h_context = 0;

bool Generic_CUDA_Init()
{
	if(cuInit(0) != CUDA_SUCCESS) {
		//fprintf(stderr, "error: failed to initialize CUDA\n"); // this is printed in main already
		return false;
	}
	// initialize CUDA

	int n_driver_version;
	cuDriverGetVersion(&n_driver_version);
	// get driver version

	printf("CUDA driver version %d\n", n_driver_version);

	int n_device_num;
	cuDeviceGetCount(&n_device_num);
	// get device count

	for(int i = 0; i < n_device_num; ++ i) {
		CCuDeviceParams dev_params(i);
		if(!dev_params.b_Status()) {
			fprintf(stderr, "error: failed to get CUDA device %d\n", i);
			return false;
		}
		// get device handle

		printf("device(id: %d, \'%s\', " PRIsizeB
			"B RAM, %.2f MHz, multiproc: %d, max-threads-block: %d, max-block-size: %dx%dx%d)\n",
			dev_params.n_DeviceIndex(), dev_params.p_s_Name(),
			PRIsizeBparams(dev_params.n_Memory_Size()),
			dev_params.t_Properties().clockRate / 1e3f,
			dev_params.n_Multiprocessor_Num(),
			dev_params.t_Properties().maxThreadsPerBlock,
			dev_params.t_Properties().maxThreadsDim[0],
			dev_params.t_Properties().maxThreadsDim[1],
			dev_params.t_Properties().maxThreadsDim[2]);
		// show some device parameters
	}
	// show some information about devices

	int n_device = CCuDeviceParams::n_Get_MaxGFlops_DeviceId();
	printf("using device %d ...\n", n_device);
	// show which device we're going to use from now on

	CUdevice h_device;
	if(cuDeviceGet(&h_device, n_device) != CUDA_SUCCESS) {
		fprintf(stderr, "error: failed to get CUDA device %d\n", n_device);
		return false;
	}
	// get device handle

	if(cuCtxCreate(&h_context, CU_CTX_SCHED_AUTO, h_device) != CUDA_SUCCESS) {
		fprintf(stderr, "error: failed to create CUDA context\n");
		return false;
	}
	// create CUDA context

	return true;
}

void Generic_CUDA_Shutdown()
{
	if(h_context) {
		cuCtxDetach(h_context);
		cuCtxDestroy(h_context);
		h_context = 0;
	}
	// detach context
}

void TrimSpace(std::string &r_s_string)
{
	int b = 0, e = r_s_string.length();
	while(e > 0 && isspace(r_s_string[e - 1]))
		-- e;
	while(b < e && isspace(r_s_string[b]))
		++ b;
	r_s_string.erase(e);
	r_s_string.erase(0, b);
}

bool ReadLine(FILE *p_fr, std::string &r_s_line, int &r_n_cur_line)
{
	while(!feof(p_fr)) {
		r_s_line.erase();
		try {
			for(int c = fgetc(p_fr); c != '\n' && c != EOF; c = fgetc(p_fr))
				r_s_line += c;
		} catch(std::bad_alloc&) {
			return false;
		}
		// read line

		++ r_n_cur_line;
		// line counter for file debugging

		if(r_s_line.find('#') != std::string::npos)
			r_s_line.erase(r_s_line.find('#'));
		// throw away line comment

		TrimSpace(r_s_line);
		// throw away begin / end whitespace

		if(!r_s_line.length())
			continue;
		// skip empty lines

		return true;
	}

	return false;
}

bool Load_LightSVM_File(const char *p_s_filename, CVectorDataModel<float, int> &r_model)
{
	FILE *p_fr;
	if(!(p_fr = fopen(p_s_filename, "r"))) {
		fprintf(stderr, "error: failed to open \'%s\'\n", p_s_filename);
		return false;
	}
	// open file

	int n_first_feature = INT_MAX;
	int n_last_feature = 0;
	int n_point_num = 0;

	printf("scanning file \'%s\' ...\n", p_s_filename);

	for(int n_pass = 0; n_pass < 2; ++ n_pass) {
		if(n_pass) {
			fseek(p_fr, 0, SEEK_SET);
			// go to the beginning of the file

			printf("allocating %d points x %d dimensions\n", n_point_num, n_last_feature - n_first_feature + 1);

			if(!r_model.Allocate(n_point_num, n_last_feature - n_first_feature + 1)) {
				fprintf(stderr, "error: not enough memory to load file \'%s\'\n", p_s_filename);
				fclose(p_fr);
				return false;
			}
			// allocate model

			n_point_num = 0;

			printf("reading file ...\n");
		}

		int n_line_counter = 0;
		std::string s_line;
		while(ReadLine(p_fr, s_line, n_line_counter)) {
			int b = 0, e = s_line.length();
			int n_class_label = (n_pass)? atol(s_line.c_str() + b) : 0;
			if(b < e && (s_line[b] == '+' || s_line[b] == '-'))
				++ b;
			if(b == e || !isdigit(s_line[b])) {
				fprintf(stderr, "error: file \'%s\', line %d: invalid class label\n",
					p_s_filename, n_line_counter);
				fclose(p_fr);
				return false;
			}
			while(b < e && isdigit(s_line[b]))
				++ b;
			// skip class label

			if(n_pass)
				r_model.Set_Target(n_point_num, n_class_label);
			// save class label

			while(b < e) {
				while(b < e && isspace(s_line[b]))
					++ b;
				// skip whitespace

				if(b == e)
					break;
				if(!isdigit(s_line[b])) {
					fprintf(stderr, "error: file \'%s\', line %d: invalid feature index\n",
						p_s_filename, n_line_counter);
					fclose(p_fr);
					return false;
				}
				int n_feature_index = atol(s_line.c_str() + b);
				while(b < e && isdigit(s_line[b]))
					++ b;
				// skip feature index

				if(b == e || s_line[b] != ':') {
					fprintf(stderr, "error: file \'%s\', line %d: missing \':\' after feature index\n",
						p_s_filename, n_line_counter);
					fclose(p_fr);
					return false;
				}
				++ b;
				// there must be ':'

				if(!n_pass) {
					if(n_first_feature > n_feature_index)
						n_first_feature = n_feature_index;
					if(n_last_feature < n_feature_index)
						n_last_feature = n_feature_index;
				}
				// find minmax features in the first pass

				if(b == e || (s_line[b] != '+' && s_line[b] != '-' && !isdigit(s_line[b]) && s_line[b] != '.')) {
					fprintf(stderr, "error: file \'%s\', line %d: invalid feature value\n",
						p_s_filename, n_line_counter);
					fclose(p_fr);
					return false;
				}
				float f_feature_value = (n_pass)? atof(s_line.c_str() + b) : 0;
				for(;;) {
					if(s_line[b] == '+' || s_line[b] == '-')
						++ b;
					// skip sign

					if(b == e || (!isdigit(s_line[b]) && s_line[b] != '.')) {
						fprintf(stderr, "error: file \'%s\', line %d: invalid feature value\n",
							p_s_filename, n_line_counter);
						fclose(p_fr);
						return false;
					}
					while(b < e && isdigit(s_line[b]))
						++ b;
					// skip integer part

					if(b == e || isspace(s_line[b]))
						break;
					// there are spaces, which means that was it

					if(b < e && s_line[b] == '.') {
						++ b;

						if(b == e || (!isdigit(s_line[b]) && tolower(s_line[b]) != 'e')) {
							fprintf(stderr, "error: file \'%s\', line %d: invalid feature value\n",
								p_s_filename, n_line_counter);
							fclose(p_fr);
							return false;
						}
						// there must be more numbers, or at least exponent

						while(b < e && isdigit(s_line[b]))
							++ b;
						// skip fractional part

						if(b == e || isspace(s_line[b]))
							break;
						// there are spaces, which means that was it

						if(tolower(s_line[b]) != 'e') {
							fprintf(stderr, "error: file \'%s\', line %d: invalid feature value\n",
								p_s_filename, n_line_counter);
							fclose(p_fr);
							return false;
						}
						++ b;
						// otherwise there must be exponent

						if(b == e || (s_line[b] != '+' && s_line[b] != '-' && !isdigit(s_line[b]))) {
							fprintf(stderr, "error: file \'%s\', line %d: invalid feature value\n",
								p_s_filename, n_line_counter);
							fclose(p_fr);
							return false;
						}
						if(s_line[b] == '+' || s_line[b] == '-')
							++ b;
						// skip sign

						while(b < e && isdigit(s_line[b]))
							++ b;
						// skip integer part

						if(b < e && !isspace(s_line[b])) {
							fprintf(stderr, "error: file \'%s\', line %d: invalid feature value\n",
								p_s_filename, n_line_counter);
							fclose(p_fr);
							return false;
						}
						// there are spaces, which means that was it
					} else {
						fprintf(stderr, "error: file \'%s\', line %d: invalid feature value\n",
							p_s_filename, n_line_counter);
						fclose(p_fr);
						return false;
					}
					// skip decimal part

					break;
				}
				// there must be float

				if(n_pass)
					r_model.Set_Point(n_point_num, n_feature_index - n_first_feature, f_feature_value);
				// puts point to the model
			}
			// read feature values

			++ n_point_num;
			// count points in the first pass

			if(n_point_num >= 10000)
				break;
		}
	}
	// 

	fclose(p_fr);
	// close file

	return true;
}

int main(int n_arg_num, const char **p_arg_list)
{
	if(!Generic_CUDA_Init()) {
		fprintf(stderr, "error: failed to initialize CUDA\n");
		return -1;
	}
	// init CUDA

	CUmodule h_module;
	if(cuModuleLoad(&h_module, "SMOCore.ptx") != CUDA_SUCCESS) {
		fprintf(stderr, "error: failed to load CUDA module \'%s\'\n", "SMOCore.ptx");
		Generic_CUDA_Shutdown();
		return -1;
	}
	// load CUDA module

	const double C = .5;
	const bool b_use_linear_kernel = false;

	CVectorDataModel<float, int> svm_data;
	if(!Load_LightSVM_File("E:\\my-projects\\z_large_datafiles\\SVM_Sets\\faces\\faces4svm.dat", svm_data)) {
	//if(!Load_LightSVM_File("E:\\my-projects\\z_large_datafiles\\SVM_Sets\\faces\\adult.dat", svm_data)) {
		fprintf(stderr, "error: failed to load SVM training data\n");
		Generic_CUDA_Shutdown();
		return -1;
	}

	{
		std::vector<float> maxima;
		if(!stl_ut::Resize_To_N(maxima, svm_data.n_Dimension_Num(), 1.0f)) {
			fprintf(stderr, "error: not enough memory\n");
			Generic_CUDA_Shutdown();
			return -1;
		}
		for(size_t i = 0, n = svm_data.n_Point_Num(), m = svm_data.n_Dimension_Num(); i < n; ++ i) {
			for(size_t j = 0; j < m; ++ j)
				maxima[j] = max(maxima[j], fabs(svm_data.f_Get_Point(i, j)));
		}
		for(size_t j = 0, m = svm_data.n_Dimension_Num(); j < m; ++ j)
			maxima[j] = 1 / maxima[j];
		for(size_t i = 0, n = svm_data.n_Point_Num(), m = svm_data.n_Dimension_Num(); i < n; ++ i) {
			for(size_t j = 0; j < m; ++ j)
				svm_data.Set_Point(i, j, svm_data.f_Get_Point(i, j) * maxima[j]);
		}
	}
	// normalize input data to aid numerical stability

	for(int n_pass = 0; n_pass < 1; ++ n_pass) {
		printf("training ...\n");

		CTimer timer;
		double f_start_time = timer.f_Time();
		{
			CSMOonGPU smo(h_module);
			//CSMO smo;
			// GPU vs CPU

			if(!smo.SetModel(&svm_data, b_use_linear_kernel)) {
				fprintf(stderr, "error: CSMO::SetModel() failed (not enough memory)\n");
				return -1;
			}
			if(!smo.Train(C, 1e-3f, 1e-3f, 30000, true))
				fprintf(stderr, "error: SMO failed to train SVM in maximal number of passes\n");

			float f_err = smo.f_ErrorRate();
			printf("SVM learned %.2f%% of training examples \n", (1 - f_err) * 100);
			// dump error rate on training examples
		}
		double f_time = timer.f_Time() - f_start_time;
		printf("it took " PRItime "\n", PRItimeparams(f_time));
	}

	/*const int n_example_num = 1000;
	const int n_pass_num = 100;
	for(int n_pass = 0; n_pass < n_pass_num; ++ n_pass) {
		CVectorDataModel<float, int> svm_data;
		if(!svm_data.Allocate(n_example_num, 3)) {
			fprintf(stderr, "error: not enough memory for SMO\n");
			return -1;
		}
		std::vector<Vector3f> used_example_list;
		for(int i = 0; i < n_example_num; ++ i) {
			Vector3f v_point;
			for(int n_gen = 0;; ++ n_gen) {
				v_point = (i % 2)? v_RandGroupPoint(Vector3f(-.5f, -.5f, -.5f), .5f) :
					v_RandGroupPoint(Vector3f(.5f, .5f, .5f), .5f);
				v_point = v_point * .5f + .5f; // normalize to [0, 1]
				if(std::find_if(used_example_list.begin(), used_example_list.end(),
				   CComparePt(v_point)) == used_example_list.end()) {
					used_example_list.push_back(v_point);
					break;
				} else if(n_gen > 10000) {
					fprintf(stderr, "error: it's impossible to stuff %d "
						"different points into cloud this small\n", n_example_num);
					return -1;
				}
			}
			svm_data.Set_Point(i, 0, v_point.x);
			svm_data.Set_Point(i, 1, v_point.y);
			svm_data.Set_Point(i, 2, v_point.z);
			svm_data.Set_Target(i, (i % 2)? +1 : -1);
			// give equal amount of positive and negative examples
		}
		printf("learning (pass %d of %d) ...\n", n_pass + 1, n_pass_num);

		{
			CSMOonGPU smo(h_module);
			//CSMO smo;
			if(!smo.SetModel(&svm_data)) {
				fprintf(stderr, "error: CSMO::SetModel() failed (not enough memory)\n");
				return -1;
			}
			if(!smo.Train(C, 1e-3f, 1e-3f, 15000, true))
				fprintf(stderr, "error: SMO failed to train SVM in maximal number of passes\n");

			float f_err = smo.f_ErrorRate();
			printf("SVM learned %.2f%% of training examples \n", (1 - f_err) * 100);
			// dump error rate on training examples
		}
	}*/
	// dummy test

	cuModuleUnload(h_module);
	// free CUDA module

	Generic_CUDA_Shutdown();
	// shutdown CUDA

	return 0;
}
